﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using MongoDB.Driver;

namespace MongoDb.EntityFramework
{
    [ComVisible(true)]
    public class MongoRepository : IRepository
    {
        private IMongoDatabase db;
        private MongoClient mongoClient;

        private IMongoDatabase GetDatabase(MongoUrl url)
        {
            this.mongoClient = new MongoClient(url);

            return this.mongoClient.GetDatabase(url.DatabaseName);
        }

        public MongoRepository(string connectionString)
        {
            var connStringFromManager =
                    MongoUrl.Create(connectionString);

            this.db = GetDatabase(connStringFromManager);
        }

        public MongoRepository(IMongoDatabase database)
        {
            this.db = database;
        }

        public void Add<T>(IEnumerable<T> items) where T : class, new()
        {
            db.GetCollection<T>(typeof(T).Name).InsertManyAsync(items).Wait();
        }

        public void Add<T>(T item) where T : class, new()
        {
            db.GetCollection<T>(typeof(T).Name).InsertOneAsync(item).Wait();
        }

        public IQueryable<T> All<T>() where T : class, new()
        {
            var task = this.db.GetCollection<T>(typeof(T).Name).CountAsync(_ => true);
            task.Wait();

            return All<T>(1, Convert.ToInt32(task.Result));
        }

        public IQueryable<T> All<T>(int page, int pageSize) where T : class, new()
        {
            var task = this.db.GetCollection<T>(typeof(T).Name).Find(_ => true).Skip((page - 1) * pageSize).Limit(pageSize).ToListAsync();
            task.Wait();

            return task.Result.AsQueryable();
        }

        public void Delete<T>(T item) where T : class, new()
        {
            this.db.GetCollection<T>(typeof(T).Name).DeleteOneAsync(u => u.Equals(item)).Wait();
        }

        public void Delete<T>(Expression<Func<T, bool>> expression) where T : class, new()
        {
            this.db.GetCollection<T>(typeof(T).Name).DeleteManyAsync<T>(expression).Wait();
        }

        public void DeleteAll<T>() where T : class, new()
        {
            this.db.DropCollectionAsync(typeof(T).Name).Wait();
        }

        public void Dispose()
        {
            this.db = null;
            this.mongoClient = null;
        }

        public T Single<T>(Expression<Func<T, bool>> expression) where T : class, new()
        {
            var task = this.db.GetCollection<T>(typeof(T).Name).Find(expression).FirstOrDefaultAsync();
            task.Wait();

            return task.Result;
        }

        public IEnumerable<T> Where<T>(Expression<Func<T, bool>> expression) where T : class, new()
        {
            var task = this.db.GetCollection<T>(typeof(T).Name).Find(expression).ToListAsync();
            task.Wait();

            return task.Result;
        }

        public void Update<T>(T item, Expression<Func<T, bool>> expression) where T : class, new()
        {
            this.db.GetCollection<T>(typeof(T).Name).ReplaceOneAsync<T>(expression, item).Wait();
        }

        public void Update<T>(IEnumerable<T> items, Expression<Func<T, bool>> expression) where T : class, new()
        {
            foreach (T item in items)
            {
                Update(item, expression);
            }
        }
    }
}